package com.seleniti.enpareja.web.desktop.client.ui.view;

import static com.google.gwt.dom.client.BrowserEvents.CHANGE;
import static com.google.gwt.dom.client.BrowserEvents.CLICK;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.ioc.commons.impl.gwt.client.ui.HasEnabledWrapper;
import org.ioc.commons.impl.gwt.client.ui.HasValueWrapper;
import org.ioc.commons.impl.gwt.shared.common.util.WrappedDeckPanelAsEnum;
import org.ioc.commons.ui.HasValue;
import org.ioc.commons.ui.impl.HasValueAdapter;
import org.ioc.commons.utils.Format;
import org.ioc.commons.utils.FormatterLogger;

import com.google.gwt.cell.client.AbstractSafeHtmlCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellList;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.CustomPushButton;
import com.google.gwt.user.client.ui.DeckLayoutPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.NoSelectionModel;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.model.integration.database.domain.ShoppingListElement;
import com.seleniti.enpareja.controller.ui.view.ShoppingListView;
import com.seleniti.enpareja.controller.ui.view.ShoppingListView.Action;
import com.seleniti.enpareja.controller.ui.view.ShoppingListView.Event;
import com.seleniti.enpareja.controller.ui.view.ShoppingListView.Operation;
import com.seleniti.enpareja.controller.ui.view.ShoppingListView.Presenter;
import com.seleniti.enpareja.controller.ui.view.ShoppingListViewPresenter;
import com.seleniti.enpareja.web.desktop.client.ui.cell.EnParejaTemplate;
import com.seleniti.enpareja.web.desktop.client.ui.resources.CommonResources;
import com.seleniti.enpareja.web.desktop.client.ui.widget.Checker;
import com.seleniti.enpareja.web.desktop.client.ui.widget.Checker.SelectionMode;
import com.seleniti.enpareja.web.desktop.client.ui.widget.FloatingTipWidget;
import com.seleniti.enpareja.web.desktop.client.ui.widget.PendingMark;

public class ShoppingListViewWidget extends EnParejaBaseView<Event, Operation, Action, Presenter> implements
		ShoppingListView {

	private static final FormatterLogger logger = FormatterLogger.getLogger(ShoppingListViewWidget.class);

	private static TemplateUiBinder uiBinder = GWT.create(TemplateUiBinder.class);

	interface TemplateUiBinder extends UiBinder<Widget, ShoppingListViewWidget> {
	}

	private static EnParejaTemplate template = GWT.create(EnParejaTemplate.class);

	public static interface Style extends CssResource {
		String container();

		String columnsHeader();

		String weHave();

		String weDontHave();

		String amount();

		String down();
	}

	@UiField(provided = true)
	CellList<ShoppingListElement> cellList;

	@UiField
	Style style;

	@UiField
	CustomPushButton btnAdd;

	@UiField
	ToggleButton btnModify;

	@UiField
	ToggleButton btnSetAmount;

	@UiField
	ToggleButton btnDelete;

	@UiField
	CommonResources common;

	@UiField(provided = true)
	Checker<ShoppingListElement> checker;

	@UiField
	ToggleButton weHaveFilter;

	@UiField
	PendingMark weDontHaveMark;

	@UiField
	Panel markContainer;

	@UiField
	DeckLayoutPanel mainPanel;

	@UiField(provided = true)
	CellList<String> basicProductsList;

	@UiField(provided = true)
	Checker<String> basicProductsChecker;

	private ListDataProvider<ShoppingListElement> listDataProvider;

	private boolean loading;

	private ProvidesKey<ShoppingListElement> keyProvider;

	private ActionMode actionMode;

	private boolean enabled = true;

	private WrappedDeckPanelAsEnum<Board> boardShown;

	private ListDataProvider<String> basicProductsProvider;

	public ShoppingListViewWidget() {
		this.presenter = new ShoppingListViewPresenter(this, eventbus, operationManager, actionController);

		this.keyProvider = new ProvidesKey<ShoppingListElement>() {

			@Override
			public Object getKey(ShoppingListElement item) {
				return item != null ? item.getShoppingListElementId() : null;
			}
		};
		SafeHtmlRenderer<ShoppingListElement> renderer = new AbstractSafeHtmlRenderer<ShoppingListElement>() {

			@Override
			public SafeHtml render(ShoppingListElement object) {
				if (object != null) {
					SafeHtml listElement = null;
					String text = object.getText();

					if (getActionMode() == ActionMode.SET_AMOUNT) {
						SafeHtml amount = template.inputNumber(style.amount(), "0", "", "1", " #",
								object.getAmount() != null ? object.getAmount().toString() : "");

						listElement = template.listElement(
								(object.isWeHave() ? common.style().tri_left() + " " + style.weHave() : common.style()
										.tri_right() + " " + style.weDontHave()), text);

						if (!object.isWeHave()) {
							listElement = template.table2Colums(style.weDontHave() + " " + style.amount(),
									style.amount(), amount, "", listElement);
						} else {
							listElement = template.table2Colums(style.weHave() + " " + style.amount(), "", listElement,
									style.amount(), amount);
						}
					} else {

						if (object.getAmount() != null && object.getAmount() > 0) {
							text = Format.substitute("{0} {1}", object.getAmount(), text);
						}

						listElement = template.listElement(
								(object.isWeHave() ? common.style().tri_left() + " " + style.weHave() : common.style()
										.tri_right() + " " + style.weDontHave()), text);
					}

					return listElement;
				} else {
					return SafeHtmlUtils.fromString("-- no element --");
				}
			}
		};
		this.cellList = new CellList<ShoppingListElement>(new AbstractSafeHtmlCell<ShoppingListElement>(renderer,
				"click", "change") {

			@Override
			protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) {
				if (data != null) {
					sb.append(data);
				}
			}

			public void onBrowserEvent(Context context, Element parent, ShoppingListElement value, NativeEvent event,
					ValueUpdater<ShoppingListElement> valueUpdater) {
				super.onBrowserEvent(context, parent, value, event, valueUpdater);
				if (CLICK.equals(event.getType())) {
					EventTarget eventTarget = event.getEventTarget();
					if (!Element.is(eventTarget)) {
						return;
					}
					Element elm = Element.as(eventTarget);
					if (parent.getFirstChildElement().isOrHasChild(elm)) {
						// Ignore clicks that occur outside of the main element.
						if (valueUpdater != null) {
							valueUpdater.update(value);
						}
					}
				} else if (CHANGE.equals(event.getType())) {
					EventTarget eventTarget = event.getEventTarget();
					if (!Element.is(eventTarget)) {
						return;
					}
					Element elm = Element.as(eventTarget);
					if (parent.getFirstChildElement().isOrHasChild(elm) && InputElement.is(elm)) {
						Integer amount;
						try {
							amount = Integer.valueOf(InputElement.as(elm).getValue());
						} catch (NumberFormatException e) {
							amount = null;
						}
						presenter.onAmountChanged(value, amount);
					}
				}
			}

		}, keyProvider);

		this.cellList.setPageSize(Integer.MAX_VALUE);

		this.cellList.setValueUpdater(new ValueUpdater<ShoppingListElement>() {

			@Override
			public void update(ShoppingListElement value) {
				if (actionMode != null) {
					switch (actionMode) {
					case RENAME_ELEMENT:
						renameElement(value);
						break;
					case SWITCH_LIST:
						switchElementFromList(value);
						break;
					case DELETE_ELEMENT:
					case SET_AMOUNT:
					default:
						/*
						 * Nothing to do
						 */
					}
				}
			}
		});

		HTML empty = new HTML("Ningún elemento en tu lista");
		this.cellList.setEmptyListWidget(empty);

		this.cellList.setSelectionModel(new NoSelectionModel<ShoppingListElement>(keyProvider));

		this.listDataProvider = new ListDataProvider<ShoppingListElement>();
		this.listDataProvider.addDataDisplay(cellList);

		this.checker = new Checker<ShoppingListElement>(50);
		this.checker.addDataDisplay(cellList);

		this.basicProductsList = new CellList<String>(new TextCell());
		this.basicProductsList.setPageSize(Integer.MAX_VALUE);
		this.basicProductsProvider = new ListDataProvider<String>();
		this.basicProductsProvider.addDataDisplay(basicProductsList);

		this.basicProductsChecker = new Checker<String>(50);
		this.basicProductsChecker.addDataDisplay(basicProductsList);

		initWidget(uiBinder.createAndBindUi(this));

		empty.setStyleName(common.style().emptyList());

	}

	@Override
	public List<ShoppingListElement> shoppingList() {
		return this.listDataProvider.getList();
	}

	@UiHandler("btnModify")
	void onModifyButtonToggled(ValueChangeEvent<Boolean> evt) {
		boolean enabled = Boolean.TRUE.equals(evt.getValue());
		this.presenter.onModifyButtonToggled(enabled);

		if (enabled) {
			btnSetAmount.setDown(false);
			btnDelete.setDown(false);
		}
	}

	@UiHandler("btnSetAmount")
	void onSetAmountButtonToggled(ValueChangeEvent<Boolean> evt) {
		boolean enabled = Boolean.TRUE.equals(evt.getValue());
		this.presenter.onSetAmountButtonToggled(enabled);

		if (enabled) {
			btnModify.setDown(false);
			btnDelete.setDown(false);
		}
	}

	@UiHandler("btnDelete")
	void onDeleteButtonToggled(ValueChangeEvent<Boolean> evt) {
		boolean enabled = Boolean.TRUE.equals(evt.getValue());
		this.setActionMode(enabled ? ActionMode.DELETE_ELEMENT : ActionMode.SWITCH_LIST);

		if (enabled) {
			btnSetAmount.setDown(false);
			btnModify.setDown(false);
		}
	}

	@UiHandler("btnAdd")
	void onAddElementButtonClicked(ClickEvent evt) {
		logger.info("Button add clicked");
		this.addNewElement();
	}

	@UiHandler("checker")
	void onCheckerSelectionChanged(SelectionChangeEvent evt) {
		presenter.onSelectionChanged();
	}

	@Override
	public void addNewElement() {
		this.presenter.onAddNewElement();
	}

	@Override
	public void renameElement(ShoppingListElement element) {
		this.presenter.onRenameElement(element);
	}

	@Override
	public void setLoading(boolean loading) {
		this.loading = loading;
		btnAdd.setEnabled(!loading);
		btnModify.setEnabled(!loading);
		btnDelete.setEnabled(!loading);
		btnSetAmount.setEnabled(!loading);
	}

	@Override
	public boolean isLoading() {
		return this.loading;
	}

	@Override
	public void setActionMode(ActionMode mode) {
		this.actionMode = mode;
		switch (mode) {
		case DELETE_ELEMENT:
			checker.setSelectionMode(SelectionMode.Multiselection);
			checker.setVisible(true);
			btnDelete.setDown(true);
			btnModify.setDown(false);
			btnSetAmount.setDown(false);
			break;
		case SWITCH_LIST:
			checker.setVisible(false);
			btnDelete.setDown(false);
			btnModify.setDown(false);
			btnSetAmount.setDown(false);
			break;
		case RENAME_ELEMENT:
			checker.setSelectionMode(SelectionMode.SingleSelection);
			checker.setVisible(true);
			btnDelete.setDown(false);
			btnModify.setDown(true);
			btnSetAmount.setDown(false);
			break;
		case SET_AMOUNT:
			checker.setVisible(false);
			btnDelete.setDown(false);
			btnModify.setDown(false);
			btnSetAmount.setDown(true);
			break;
		}

		cellList.redraw();
		this.presenter.onActionModeChanged();
	}

	@Override
	public ActionMode getActionMode() {
		return actionMode;
	}

	public void switchElementFromList(ShoppingListElement value) {
		presenter.onSwitchElementFromList(value);
	}

	@Override
	public Set<ShoppingListElement> getSelection() {
		Set<ShoppingListElement> selection = new HashSet<ShoppingListElement>();
		boolean selected[] = this.checker.getSelection();
		for (int i = 0; i < selected.length; i++) {
			boolean elmSelected = selected[i];

			if (elmSelected) {
				selection.add(this.shoppingList().get(i));
			}
		}

		return selection;
	}

	@Override
	public boolean isEnabled() {
		return this.enabled;
	}

	@Override
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
		HasEnabledWrapper.setEnabled(enabled, btnAdd, btnDelete, btnModify, btnSetAmount);
		this.setTitle(enabled ? "" : "¡Asociate a tu pareja antes de empezar a usar todas las opciones!");
	}

	@UiHandler("weHaveFilter")
	void onWeHaveFilter(ValueChangeEvent<Boolean> evt) {
		this.presenter.onWeHaveFilterChanged();
		markContainer.setStyleName(style.down(), evt.getValue());
	}

	@UiHandler("weHaveFilter2")
	void onWeHaveFilter2ButtonClicked(ClickEvent evt) {
		weHaveFilter.setValue(!weHaveFilter.getValue(), true);
	}

	@UiHandler("btnAddBasics")
	void onAddBasicsButtonClicked(ClickEvent evt) {
		presenter.onAddSelectedBasicElements();
	}

	@UiHandler("btnCancelBasics")
	void onCancelBasicsButtonClicked(ClickEvent evt) {
		presenter.onCancelAddingSelectedBasicElements();
	}

	@Override
	public HasValue<Boolean> weDontHaveFilter() {
		return HasValueWrapper.from(weHaveFilter, true, this);
	}

	@Override
	public HasValue<Integer> weDontHaveCounter() {
		return this.weDontHaveMark;
	}

	@Override
	public void scrollToElement(final int idx) {
		Scheduler.get().scheduleDeferred(new ScheduledCommand() {

			@Override
			public void execute() {
				Element rowElement = cellList.getRowElement(idx);
				if (rowElement != null) {
					rowElement.scrollIntoView();
				}
			}
		});
	}

	@Override
	public HasValue<Board> boardShown() {
		if (this.boardShown == null) {
			this.boardShown = new WrappedDeckPanelAsEnum<ShoppingListView.Board>(mainPanel, Board.values());
		}
		return boardShown;
	}

	@Override
	public List<String> basicProducts() {
		return this.basicProductsProvider.getList();
	}

	@Override
	public Set<String> selectedBasicProducts() {

		return this.basicProductsChecker.getSelectedItems();
	}

	@Override
	public void showBasics() {
		presenter.onShowBasics();
	}

	@Override
	public boolean goBoardBack() {
		return presenter.onGoBoardBack();
	}

	@Override
	public HasValue<LoggedUserInfo> loggedUserInfo() {
		return HasValueAdapter.<LoggedUserInfo> forField("loggedUserInfo", this);
	}

	@Override
	public void showAllTips() {
		showTips(HelpTips.values());
	}

	@Override
	public void hideAllTips() {
		hideTips(HelpTips.values());
	}

	private final Map<HelpTips, PopupPanel> showingTips = new HashMap<>();

	@Override
	public void showTips(HelpTips... tips) {
		for (HelpTips tip : tips) {
			showTip(tip);
		}

	}

	@Override
	public void showTip(final HelpTips tip) {
		
		Timer t = new Timer() {
			
			@Override
			public void run() {
				if(showingTips.containsKey(tip)) {
					/**
					 * Showing now
					 */
					hideTip(tip);
				}
				
				UIObject target = null;
				switch (tip) {
				case BASIC_PRODUCTS:
					// target = btn
					break;
				case FILTER_WE_DONT_HAVE:
					target = weHaveFilter;
					break;
				case SCHEDULE_GO_TO_MARKET:
					break;
				case SET_AMOUNTS:
					target = btnSetAmount;
					break;
				case SWITCH_PRODUCTS:
					target = cellList;
					break;
				default:
					throw new IllegalArgumentException("Unknown tip " + tip);
				}
				if (target != null) {
					PopupPanel pp = FloatingTipWidget.alignTo(target, tip.getText());
					showingTips.put(tip, pp);
				}
			}
		};
		t.schedule(1000);
	}

	@Override
	public HelpTips[] showingTips() {
		return showingTips.keySet().toArray(new HelpTips[showingTips.size()]);
	}

	@Override
	public void hideTips(HelpTips... tips) {
		for (HelpTips tip : tips) {
			hideTip(tip);
		}
	}

	@Override
	public void hideTip(HelpTips tip) {
		PopupPanel pp = showingTips.remove(tip);
		if (pp != null) {
			pp.hide();
		}
	}
}
